/*
 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package org.eclipse.imagen;

import java.awt.RenderingHints;
import java.awt.image.renderable.ContextualRenderedImageFactory;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderedImageFactory;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import org.eclipse.imagen.media.util.PropertyUtil;
import org.eclipse.imagen.media.util.Service;
import org.eclipse.imagen.registry.CIFRegistry;
import org.eclipse.imagen.registry.CRIFRegistry;
import org.eclipse.imagen.registry.CollectionRegistryMode;
import org.eclipse.imagen.registry.RIFRegistry;
import org.eclipse.imagen.registry.RenderableRegistryMode;
import org.eclipse.imagen.registry.RenderedRegistryMode;
import org.eclipse.imagen.util.CaselessStringKey;
import org.eclipse.imagen.util.ImagingException;
import org.eclipse.imagen.util.ImagingListener;

/**
 * A class responsible for maintaining a registry of various types of factory objects and preferences among them. The
 * operation registry hierarchy looks as follows
 *
 * <pre>
 *
 *                                                      |-object1-
 *                                           |-product1-|-object2-
 *                             |-descriptor1-|          |-object3-
 *                             |             |
 *                             |             |          |-object1-
 *                             |             |-product2-|-object2-
 *                     |-mode1-|                        |-object3-
 *                     |       |
 * |-OperationRegistry-|       |                        |-object1-
 *                     |       |             |-product1-|-object2-
 *                     |       |-descriptor2-|          |-object3-
 *                     |                     |
 *                     |                     |          |-object1-
 *                     |                     |-product2-|-object2-
 *                     |                                |-object3-
 *                     |
 *                     |-mode2-|-descriptor1-|-object1--
 *                             |
 *                             |-descriptor2-|-object1--
 * </pre>
 *
 * <p>The <code>OperationRegistry</code> class maps a descriptor name (for example, an image operation name) into the
 * particular kind of factory object requested, capable of implementing the functionality described by the descriptor.
 * The mapping is constructed in several stages:
 *
 * <p>At the highest level all objects are registered against some mode. A mode is specified by a <code>String</code>
 * which must be one of those returned by <code>RegistryMode.getModeNames()</code>. Examples of known registry modes
 * include "rendered", "renderable", "collection", "renderableCollection", "tileEncoder", "tileDecoder",
 * "remoteRendered", "remoteRenderable", etc.
 *
 * <p>Each registry mode is associated with a <code>
 * RegistryElementDescriptor</code> which describes some functionality to be implemented by factory objects associated
 * with this descriptor. For example, the "rendered" registry mode is associated with <code>OperationDescriptor.class
 * </code> and "tileEncoder" is associated with <code>TileCodecDescriptor.class</code>. Different registry modes can
 * share the same <code>
 * RegistryElementDescriptor</code>. For example "rendered", "renderable" (and other image operation registry modes) are
 * all associated with <code>OperationDescriptor.class</code>.
 *
 * <p>If a registry mode supports preferences (for example "rendered", "tileEncoder" etc.), then the hierarchy of
 * objects registered under that mode looks like that of "mode1" above. Descriptors are first registered against all
 * modes that the specific instance supports. Each factory object that implements the functionality specified by that
 * descriptor is registered against some product (name) under that descriptor. Preferences can be set among products
 * under a given descriptor or among objects under a specific product/descriptor.
 *
 * <p>The ordering of such factory objects is determined by the order of the products attached to an <code>
 * OperationDescriptor</code>, and by the order of the factory objects within each product. The orders are established
 * by setting pairwise preferences, resulting in a partial order which is then sorted topologically. The results of
 * creating a cycle are undefined.
 *
 * <p>The ordering of factory objects within a product is intended to allow vendors to create complex "fallback" chains.
 * An example would be installing a <code>RenderedImageFactory</code> that implements separable convolution ahead of a
 * <code>RenderedImageFactory</code> that implements a more general algorithm.
 *
 * <p>If a registry mode does not support preferences (for example, "renderable", "remoteRenderable" etc.) then the
 * hierarchy of objects registered under that mode looks like that of "mode2" above. Only a single factory object
 * belonging to this mode can be associated with a given descriptor. If multiple objects are registered under the same
 * descriptor, the last one registered is retained.
 *
 * <p>The <code>OperationRegistry</code> has several methods to manage this hierarchy, which accept a <code>modeName
 * </code> and work with <code>
 * Object</code>s. The preferred manner of usage is through the type-safe wrapper class which are specific to each mode
 * (for example <code>
 * RIFRegistry</code>, <code>CRIFRegistry</code> etc.)
 *
 * <p>Vendors are encouraged to use unique product names (by means of the Java programming language convention of
 * reversed Internet addresses) in order to maximize the likelihood of clean installation. See <i>The Java Programming
 * Language</i>, &sect;10.1 for a discussion of this convention in the context of package naming.
 *
 * <p>Users will, for the most part, only wish to set ordering preferences on the product level, since the factory
 * object level orderings will be complex. However, it is possible for a knowledgable user to insert a specific factory
 * object into an existing product for tuning purposes.
 *
 * <p>The <code>OperationRegistry</code> also has the responsibility of associating a set of <code>PropertyGenerators
 * </code> with each descriptor. This set will be coalesced into a <code>PropertySource</code> suitable for use by the
 * getPropertySource() method. If several <code>PropertyGenerator</code>s associated with a particular descriptor
 * generate the same property, only the last one to be registered will have any effect.
 *
 * <p>The registry handles all names (except class names) in a case-insensitive but retentive manner.
 *
 * <p><strong>Initialization and automatic loading of registry objects. </strong>
 *
 * <p>The user has two options for automatic loading of registry objects.
 *
 * <ul>
 *   <li>For most normal situations the user can create a "<code>registryFile.jai</code>" with entries for operators and
 *       preferences specific to their packages. This registry file must be put it in the META-INF directory of the
 *       jarfile or classpath.
 *   <li>For situations where more control over the operation registry is needed, (for example remove an operator
 *       registered by JAI etc.) the user can implement a concrete sub-class of the <code>
 * OperationRegistrySpi</code> interface and register it as service provider (see <code>
 *      OperationRegistrySpi</code> for more details). The <code>updateRegistry
 *      </code> method of such registered service providers will be called with the default <code>OperationRegistry
 *       </code> of <code>JAI</code> once it has been initialized.
 * </ul>
 *
 * <p>The initialization of the <code>OperationRegistry</code> of the default instance of <code>JAI</code> happens as
 * follows
 *
 * <ol>
 *   <li>Load the JAI distributed registry file "<code>META-INF/org.eclipse.imagen.registryFile.jai</code>" (from
 *       jai_core.jar)
 *   <li>Find and load all "<code>META-INF/registryFile.jai</code>" files found in the classpath in some arbitrary
 *       order.
 *   <li>Look for registered service providers of <code>OperationRegistrySpi</code> listed in all "<code>
 *       META-INF/services/org.eclipse.imagen.OperationRegistrySpi</code>" files found in the classpath and call their
 *       <code>updateRegistry</code> method passing in the default OperationRegistry. The order of these calls to <code>
 *       updateRegistry</code> is arbitrary.
 * </ol>
 *
 * Note that the user should not make any assumption about the order of loading WITHIN step 2 or 3. If there is a need
 * for the <code>updateRegistry</code> method to be called right after the associated registryFile.jai is read in, the
 * following could be done.
 *
 * <p>The user could give the registry file a package qualified name for e.g xxx.yyy.registryFile.jai and put this in
 * the META-INF directory of the jar file. Then in the concrete class that implements <code>OperationRegistrySpi</code>
 *
 * <pre>
 *  void updateRegistry(OperationRegistry or) {
 *      String registryFile = "META-INF/xxx.yyy.registryFile.jai";
 *      InputStream is = ClassLoader.getResourceAsStream(registryFile);
 *
 *      or.updateFromStream(is);
 *
 *      // Make other changes to "or" ...
 *  }
 * </pre>
 *
 * For information on the format of the registry file, see the <a
 * href="{@docRoot}/serialized-form.html#org.eclipse.imagen.OperationRegistry"> serialized form</a> of the <code>
 * OperationRegistry</code>.
 *
 * @see OperationRegistrySpi
 * @see RegistryMode
 * @see RegistryElementDescriptor
 */
public class OperationRegistry implements Externalizable {

    /** The JAI packaged registry file */
    static String JAI_REGISTRY_FILE = "META-INF/org.eclipse.imagen.registryFile.jai";

    /** The user defined registry files that are automatically loaded */
    static String USR_REGISTRY_FILE = "META-INF/registryFile.jai";

    /** A <code>Hashtable</code> of <code>DescritptorCache</code>s for each registry mode. */
    private Hashtable descriptors;

    /** A <code>Hashtable</code> of <code>FactoryCache</code>s for each registry mode. */
    private Hashtable factories;

    /**
     * Get the <code>FactoryCache</code> associated with a specified mode. If it does not exist but the mode is a valid
     * registry mode then silently create one.
     */
    private FactoryCache getFactoryCache(String modeName) {

        CaselessStringKey key = new CaselessStringKey(modeName);

        FactoryCache fc = (FactoryCache) factories.get(key);

        if (fc == null) {

            if (RegistryMode.getMode(modeName) != null) {
                factories.put(key, fc = new FactoryCache(modeName));

            } else {
                throw new IllegalArgumentException(JaiI18N.formatMsg("OperationRegistry0", new Object[] {modeName}));
            }
        }

        return fc;
    }

    /**
     * Get the <code>DescriptorCache</code> associated with a specified mode. If it does not exist but the mode is a
     * valid registry mode then silently create one.
     */
    private DescriptorCache getDescriptorCache(String modeName) {

        CaselessStringKey key = new CaselessStringKey(modeName);

        DescriptorCache dc = (DescriptorCache) descriptors.get(key);

        if (dc == null) {

            if (RegistryMode.getMode(modeName) != null) {
                descriptors.put(key, dc = new DescriptorCache(modeName));

            } else {
                throw new IllegalArgumentException(JaiI18N.formatMsg("OperationRegistry0", new Object[] {modeName}));
            }
        }

        return dc;
    }

    /**
     * Initialize all the internal OperationRegistry fields.
     *
     * <p>Note that this is not synchronized. It is the caller's reponsiblity to make sure that thread safety is
     * maintained.
     */
    private void initialize() {

        // Create a Hashtable to hold a DescriptorCache for each
        // known registry mode.
        descriptors = new Hashtable();

        // Create a Hashtable to hold a FactoryCache for each
        // known registry mode.
        factories = new Hashtable();
    }

    /**
     * Default Constructor. The <code>OperationRegistry</code> created is <u>not</u> thread-safe. Note that none of the
     * automatic loading of registry files or services happens here.
     *
     * @see #getThreadSafeOperationRegistry
     */
    public OperationRegistry() {
        initialize();
    }

    /**
     * Creates and returns a new thread-safe version of the <code>OperationRegistry</code> which uses reader-writer
     * locks to wrap every method with a read or a write lock as appropriate. Note that none of the automatic loading of
     * registry files or services is done on this <code>OperationRegistry</code>.
     *
     * @since JAI 1.1
     */
    public static OperationRegistry getThreadSafeOperationRegistry() {
        return new ThreadSafeOperationRegistry();
    }

    /**
     * Creates a new thread-safe <code>OperationRegistry</code>. It is initialized by first reading in the system
     * distributed registry file (<code>JAI_REGISTRY_FILE</code>), then the user installed registry files (<code>
     * USR_REGISTRY_FILE</code>) and then by calling the <code>updateRegistry()</code> method of all registered service
     * providers.
     *
     * @return a properly initialized thread-safe <code>OperationRegistry</code>
     */
    static OperationRegistry initializeRegistry() {
        try {
            InputStream url = PropertyUtil.getFileFromClasspath(JAI_REGISTRY_FILE);

            if (url == null) {
                throw new RuntimeException(JaiI18N.getString("OperationRegistry1"));
            }

            OperationRegistry registry = new ThreadSafeOperationRegistry();

            if (url != null) RegistryFileParser.loadOperationRegistry(registry, null, url);

            registry.registerServices(null);
            return registry;

        } catch (IOException ioe) {
            ImagingListener listener = JAI.getDefaultInstance().getImagingListener();
            String message = JaiI18N.getString("OperationRegistry2");
            listener.errorOccurred(message, new ImagingException(message, ioe), OperationRegistry.class, false);
            return null;

            //	    ioe.printStackTrace();
            //	    throw new RuntimeException(
            //			JaiI18N.getString("OperationRegistry2"));
        }
    }

    /**
     * Returns a String representation of the registry.
     *
     * @return the string representation of this <code>OperationRegistry</code>.
     */
    public String toString() {

        StringWriter sw = new StringWriter();

        try {
            RegistryFileParser.writeOperationRegistry(this, new BufferedWriter(sw));
            return sw.getBuffer().toString();

        } catch (Exception e) {
            return "\n[ERROR!] " + e.getMessage();
        }
    }

    /**
     * Writes out the contents of the <code>OperationRegistry</code> to a stream as specified in the <code>writeExternal
     * </code> method. For more information see the <a
     * href="{@docRoot}/serialized-form.html#org.eclipse.imagen.OperationRegistry"> serialized form</a>.
     *
     * @param out The OutputStream to which the <code>OperationRegistry</code> state is written.
     * @throws IllegalArgumentException if out is null.
     * @see #writeExternal
     */
    public void writeToStream(OutputStream out) throws IOException {
        if (out == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        RegistryFileParser.writeOperationRegistry(this, out);
    }

    /**
     * Initializes the <code>OperationRegistry</code> from an <code>InputStream</code>. All non-IO exceptions
     * encountered while parsing the registry files are caught and their error messages are redirected to <code>
     * System.err</code>. If <code>System.err</code> is null the error messages will never be seen.
     *
     * <p>The <code>InputStream</code> passed in will not be closed by this method, the caller should close the <code>
     * InputStream</code> when it is no longer needed.
     *
     * <p>The format of the data from the <code>InputStream</code> is expected to be the same as that of the <code>
     * writeExternal</code> method as specified in the <a
     * href="{@docRoot}/serialized-form.html#org.eclipse.imagen.OperationRegistry"> serialized form</a>.
     *
     * @param in The <code>InputStream</code> from which to read the data.
     * @throws IllegalArgumentException if in is null.
     * @see #writeExternal
     */
    public void initializeFromStream(InputStream in) throws IOException {

        if (in == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        initialize();
        updateFromStream(in);
    }

    /**
     * Updates the current <code>OperationRegistry</code> with the operations specified by the given <code>InputStream
     * </code>. All non-IO exceptions encountered while parsing the registry files are caught and their error messages
     * are redirected to <code>System.err</code>. If <code>System.err</code> is null the error messages will never be
     * seen.
     *
     * <p>The <code>InputStream</code> passed in will not be closed by this method, the caller should close the <code>
     * InputStream</code> when it is no longer needed.
     *
     * <p>The format of the data from the <code>InputStream</code> is expected to be the same as that of the <code>
     * writeExternal</code> method as specified in the <a
     * href="{@docRoot}/serialized-form.html#org.eclipse.imagen.OperationRegistry"> serialized form</a>.
     *
     * @param in The <code>InputStream</code> from which to read the data.
     * @throws IllegalArgumentException if in is null.
     * @see #writeExternal
     * @since JAI 1.1
     */
    public void updateFromStream(InputStream in) throws IOException {

        if (in == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        RegistryFileParser.loadOperationRegistry(this, null, in);
    }

    /**
     * Restores the contents of the registry from an ObjectInput which was previously written using the <code>
     * writeExternal</code> method.
     *
     * <p>All non-IO exceptions encountered while parsing the registry files are caught and their error messages are
     * redirected to <code>System.err</code>. If <code>System.err</code> is null the error messages will never be seen.
     *
     * @param in An ObjectInput from which to read the data.
     * @serialData The format of the data from the ObjectInput is expected to be the same as that written out by the
     *     <code>writeExternal</code> method. For more information see <a
     *     href="{@docRoot}/serialized-form.html#org.eclipse.imagen.OperationRegistry"> serialized form</a>. The current
     *     implementation is backward compatible with the old JAI 1.0.2 registry file streams.
     * @throws IllegalArgumentException if in is null.
     * @see #writeExternal
     */
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {

        if (in == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        byte barray[] = (byte[]) in.readObject();
        InputStream s = new ByteArrayInputStream(barray);
        initializeFromStream(s);
    }

    /**
     * Saves the contents of the registry as described in the <a
     * href="{@docRoot}/serialized-form.html#org.eclipse.imagen.OperationRegistry"> serialized form</a>.
     *
     * @param out An ObjectOutput to which to write the data.
     * @throws IllegalArgumentException if out is null.
     * @serialData The format of the data written to the stream is as follows. Each line in the stream can be in one of
     *     the formats described below. Space or tab characters seperate keywords in each line. The comment character is
     *     <tt>'#'</tt> (<tt>0x23</tt>); on each line all characters following the first comment character are ignored.
     *     The stream must be encoded in UTF-8.
     *     <p>
     *     <ol>
     *       <li>To register descriptors :
     *           <p><b>descriptor</b> <i>{descriptor-class-name}</i>
     *           <p><b>odesc</b> <i>{descriptor-class-name} {descriptor-name}</i>
     *           <p>
     *           <p>The second version above is deprecated and is retained for backward compatibility with JAI 1.0.2.
     *           Descriptors are always registered against <i>{descriptor-class}.getName()</i>. The
     *           <i>{descriptor-name}</i> in the second version is always ignored.
     *           <p>
     *       <li>To register factory objects under a product against a specific mode :
     *           <p><b>{registry-mode-name}</b> <i>{factory-class-name} {product-name} {descriptor-name}
     *           {local-name}</i>
     *           <p><b>{registry-mode-name}</b> <i>{factory-class-name} {descriptor-name}</i>
     *           <p>
     *           <p>The first version above is used to register factory objects against modes that support preferences.
     *           The second version is used for those that do not support preferences. <i>{local-name},</i> is an
     *           arbitrary name that is unique for a given mode. This is (only) used later on in this file to set
     *           preferences between factory objects. See class comments for {@link OperationRegistry} for a discussion
     *           on product names.
     *           <p>
     *       <li>To set preferences between products for a descriptor under a specific mode :
     *           <p><b>prefProduct {registry-mode-name}</b> <i>{descriptor-name} {preferred-product-name}
     *           {other-product-name}</i>
     *           <p><b>pref product</b> <i>{descriptor-name} {preferred-product-name} {other-product-name}</i>
     *           <p>
     *           <p>The second version above is deprecated and is retained for backward compatibility with JAI 1.0.2.
     *           This version is assumed to set product preferences for the <i>"rendered"</i> mode.
     *           <p>
     *       <li>To set preferences between factory objects for descriptor under a a specific product and registry mode
     *           :
     *           <p><b>pref</b> <i>{registry-mode-name} {descriptor-name} {product-name} {preferred-factory-local-name}
     *           {other-factory-local-name}</i>
     *           <p>
     *     </ol>
     *     <p>For example, the stream contents for an "addconst" image operation descriptor might look like this :
     *     <pre>
     *    descriptor  org.eclipse.imagen.media.opimage.AddConstDescriptor
     *
     *    rendered    org.eclipse.imagen.media.opimage.AddConstCRIF   org.eclipse.imagen.media   addconst   sunaddconstrif
     *    rendered    org.eclipse.imagen.media.opimage.AddConstRIF2   org.eclipse.imagen.media   addconst   addconstrif2
     *
     *    renderable  org.eclipse.imagen.media.opimage.AddConstCRIF   addconst
     *
     *    pref        rendered   addconst   org.eclipse.imagen.media   addconstrif2   sunaddconstrif
     * </pre>
     *     The above does the following :
     *     <ul>
     *       <li>register a descriptor for the "addconst" operator.
     *       <li>registers two <i>rendered</i> image factories.
     *       <li>register one <i>renderable</i> image factory.
     *       <li>prefer the AddConstRIF2 factory over the AddConstCRIF factory for the <i>rendered</i> mode.
     *           <p><strong>Note that JAI 1.0.2 will not be able to read the new version of the registry file
     *           streams</strong>.
     */
    public void writeExternal(ObjectOutput out) throws IOException {

        if (out == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        ByteArrayOutputStream bstream = new ByteArrayOutputStream();
        writeToStream(bstream);
        out.writeObject(bstream.toByteArray());
    }

    /** ******************** NEW JAI 1.1 methods ************************ */

    /**
     * Remove a registry mode (including pre-defined JAI modes) from the OperationRegistry. When a mode is removed, all
     * associated descriptors are also removed unless associated with another mode. Note that this does <u>not</u>
     * unregister or remove this mode from <code>RegistryMode</code> Also note that a registry mode need not be
     * explicitly added to the <code>OperationRegistry</code>. All modes registered under <code>RegistryMode</code> are
     * automatically recognized by the <code>OperationRegistry</code>.
     *
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @since JAI 1.1
     */
    public void removeRegistryMode(String modeName) {

        if (getDescriptorCache(modeName) != null) descriptors.remove(new CaselessStringKey(modeName));

        if (getFactoryCache(modeName) != null) factories.remove(new CaselessStringKey(modeName));
    }

    /**
     * Get's the list of known registry modes known to the <code>OperationRegistry</code>. This might not be all modes
     * listed in <code>RegistryMode.getModeNames()</code>.
     *
     * @since JAI 1.1
     */
    public String[] getRegistryModes() {

        Enumeration e = descriptors.keys();
        int size = descriptors.size();
        String names[] = new String[size];

        for (int i = 0; i < size; i++) {
            CaselessStringKey key = (CaselessStringKey) e.nextElement();
            names[i] = key.getName();
        }

        return names;
    }

    //////////////////
    //
    // Set of methods to register/unregister descriptors
    // with the operation registry.

    /**
     * Register a descriptor against all the registry modes it supports. The "descriptor" must be an instance of the
     * RegistryMode.getDescriptorClass() The "descriptor" is keyed on descriptor.getName(). Only one descriptor can be
     * registered against a descriptor name for a given mode.
     *
     * @param descriptor an instance of a concrete sub-class of <code>
     *     RegistryElementDescriptor</code>
     * @throws IllegalArgumentException is descriptor is <code>null</code>
     * @throws IllegalArgumentException if any of the modes returned by <code>descriptor.getSupportedModes()</code> is
     *     not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if another descriptor with the same name has already been registered against any
     *     of the modes supported by this descriptor.
     * @since JAI 1.1
     */
    public void registerDescriptor(RegistryElementDescriptor descriptor) {
        if (descriptor == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        String[] supportedModes = descriptor.getSupportedModes();

        String descriptorName = descriptor.getName();

        // First make sure that all supported modes are legal registry
        // modes.
        for (int i = 0; i < supportedModes.length; i++) {
            if (RegistryMode.getMode(supportedModes[i]) == null)
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry3", new Object[] {descriptorName, supportedModes[i]}));
        }

        // Now register the descriptor against each supported mode.
        for (int i = 0; i < supportedModes.length; i++) {

            DescriptorCache dc = getDescriptorCache(supportedModes[i]);

            dc.addDescriptor(descriptor);
        }
    }

    /**
     * Unregister a descriptor against all its supported modes from the operation registry.
     *
     * @param descriptor an instance of a concrete sub-class of <code>
     *     RegistryElementDescriptor</code>
     * @throws IllegalArgumentException is descriptor is <code>null</code>
     * @throws IllegalArgumentException if any of the modes returned by <code>descriptor.getSupportedModes()</code> is
     *     not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if any of the <code>PropertyGenerator</code>s associated with the <code>
     *     RegistryElementDescriptor</code> to be unregistered is null.
     * @since JAI 1.1
     */
    public void unregisterDescriptor(RegistryElementDescriptor descriptor) {

        if (descriptor == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        String descriptorName = descriptor.getName();

        String[] supportedModes = descriptor.getSupportedModes();

        // First make sure that all supported modes are legal registry
        // modes.
        for (int i = 0; i < supportedModes.length; i++) {
            if (RegistryMode.getMode(supportedModes[i]) == null)
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry3", new Object[] {descriptorName, supportedModes[i]}));
        }

        // Now unregister the descriptor against each supported mode.
        for (int i = 0; i < supportedModes.length; i++) {

            DescriptorCache dc = getDescriptorCache(supportedModes[i]);

            dc.removeDescriptor(descriptor);
        }
    }

    /**
     * Get the <code>RegistryElementDescriptor</code> corresponding to a <code>descriptorClass</code> and a <code>
     * descriptorName</code>. For example, <code>getDescriptor(OperationDescriptor.class, "add")</code> would get the
     * operation descriptor for the "add" image operation. Note that different descriptors might have been registered
     * against each mode associated with the descriptorClass. In this case this methods will arbitrarily return the
     * first descriptor it encounters with a matching descriptorName and descriptorClass.
     *
     * @param descriptorClass the descriptor <code>Class</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @throws IllegalArgumentException if <code>descriptorClass</code> is <code>null</code> or if the <code>
     *     descriptorClass</code> is not associated with any of the modes returned <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if descriptorName is <code>null</code>
     * @since JAI 1.1
     */
    public RegistryElementDescriptor getDescriptor(Class descriptorClass, String descriptorName) {

        if ((descriptorClass == null) || (descriptorName == null))
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        String supportedModes[] = RegistryMode.getModeNames(descriptorClass);

        if (supportedModes == null)
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry4", new Object[] {descriptorClass.getName()}));

        RegistryElementDescriptor red;

        // Now look for the descriptor in each supported mode.
        for (int i = 0; i < supportedModes.length; i++) {

            DescriptorCache dc = getDescriptorCache(supportedModes[i]);

            if ((red = dc.getDescriptor(descriptorName)) != null) return red;
        }

        return null;
    }

    /**
     * Get a <code>List</code> of all <code>RegistryElementDescriptor</code> corresponding to the <code>descriptorClass
     * </code>. For example, <code>getDescriptors(OperationDescriptor.class)</code> would get a list of all image
     * operation descriptors.
     *
     * @param descriptorClass the descriptor <code>Class</code>
     * @throws IllegalArgumentException if <code>descriptorClass</code> is <code>null</code> or if the <code>
     *     descriptorClass</code> is not associated with any of the modes returned <code>RegistryMode.getModes()</code>
     * @since JAI 1.1
     */
    public List getDescriptors(Class descriptorClass) {

        if (descriptorClass == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        String supportedModes[] = RegistryMode.getModeNames(descriptorClass);

        if (supportedModes == null)
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry4", new Object[] {descriptorClass.getName()}));

        List list;
        HashSet set = new HashSet();

        // Now unregister the descriptor against each supported mode.
        for (int i = 0; i < supportedModes.length; i++) {

            DescriptorCache dc = getDescriptorCache(supportedModes[i]);

            if ((list = dc.getDescriptors()) != null) set.addAll(list);
        }

        return new ArrayList(set);
    }

    /**
     * Get an array of all the descriptor names corresponding to the <code>descriptorClass</code>. For example, <code>
     * getDescriptorNames(OperationDescriptor.class)</code> would get an array of all image operation descriptor names.
     *
     * @param descriptorClass the descriptor <code>Class</code>
     * @throws IllegalArgumentException if <code>descriptorClass</code> is <code>null</code> or if the <code>
     *     descriptorClass</code> is not associated with any of the modes returned <code>RegistryMode.getModes()</code>
     * @since JAI 1.1
     */
    public String[] getDescriptorNames(Class descriptorClass) {

        List dlist = getDescriptors(descriptorClass);

        if (dlist != null) {

            Iterator diter = dlist.iterator();

            String[] names = new String[dlist.size()];
            int i = 0;

            while (diter.hasNext()) {
                RegistryElementDescriptor red = (RegistryElementDescriptor) diter.next();

                names[i++] = red.getName();
            }

            return names;
        }

        return null;
    }

    /**
     * Get the <code>RegistryElementDescriptor</code> corresponding to <code>descriptorName</code> which supports the
     * specified mode. This is done by matching up the <code>descriptorName</code> against <code>
     * RegistryElementDescriptor.getName</code> in a case-insensitive manner. This returns <code>null</code> if there no
     * <code>RegistryElementDescriptor</code> corresponding to <code>descriptorName</code> that supports the specified
     * mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if descriptorName is <code>null</code>
     * @since JAI 1.1
     */
    public RegistryElementDescriptor getDescriptor(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getDescriptor(descriptorName);

        return null;
    }

    /**
     * Get a list of all <code>RegistryElementDescriptor</code>s registered under a given registry mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @since JAI 1.1
     */
    public List getDescriptors(String modeName) {
        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getDescriptors();

        return null;
    }

    /**
     * Get an array of all descriptor-names of descriptors registered under a given registry mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @since JAI 1.1
     */
    public String[] getDescriptorNames(String modeName) {
        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getDescriptorNames();

        return null;
    }

    //////////////////
    //
    // Set of methods to set/unset/clear product preferences

    /**
     * Set the preference between two products for a descriptor registered under a registry mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param preferredProductName the product to be preferred.
     * @param otherProductName the other product.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code> under <code>
     *     modeName</code>.
     * @throws IllegalArgumentException if either of the products are not registered against <code>descriptorName</code>
     *     under <code>productName</code>.
     * @since JAI 1.1
     */
    public void setProductPreference(
            String modeName, String descriptorName, String preferredProductName, String otherProductName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.setProductPreference(descriptorName, preferredProductName, otherProductName);
    }

    /**
     * Remove the preference between two products for a descriptor registered under a registry mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param preferredProductName the product formerly preferred.
     * @param otherProductName the other product.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code> under <code>
     *     modeName</code>.
     * @throws IllegalArgumentException if either of the products are not registered against <code>descriptorName</code>
     *     under <code>productName</code>.
     * @since JAI 1.1
     */
    public void unsetProductPreference(
            String modeName, String descriptorName, String preferredProductName, String otherProductName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.unsetProductPreference(descriptorName, preferredProductName, otherProductName);
    }

    /**
     * Remove all the preferences between products for a descriptor registered under a registry mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if descriptorName is <code>null</code>
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @since JAI 1.1
     */
    public void clearProductPreferences(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.clearProductPreferences(descriptorName);
    }

    /**
     * Returns a list of the pairwise product preferences under a particular descriptor registered against a registry
     * mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @return an array of 2-element arrays of Strings.
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if descriptorName is <code>null</code>
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @since JAI 1.1
     */
    public String[][] getProductPreferences(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getProductPreferences(descriptorName);

        return null;
    }

    /**
     * Returns a list of the products registered under a particular descriptor in an ordering that satisfies all of the
     * pairwise preferences that have been set. Returns <code>null</code> if cycles exist. Returns null if no descriptor
     * has been registered under this descriptorName, or if no products exist for this operation.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @return a <code>Vector</code> of Strings representing product names.
     * @throws IllegalArgumentException if modeName is <code>null</code> or if the modeName is not one of the modes
     *     returned <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if descriptorName is <code>null</code>
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @since JAI 1.1
     */
    public Vector getOrderedProductList(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getOrderedProductList(descriptorName);

        return null;
    }

    //////////////////
    //
    // Set of methods to maintain factory objects to register/unregister
    // & set/unset/clear preferences & get-ordered-lists of factory
    // objects

    /**
     * Get the local name for a factory instance used in the DescriptorCache (to be used in writing out the registry
     * file).
     */
    String getLocalName(String modeName, Object factoryInstance) {

        FactoryCache fc = getFactoryCache(modeName);

        if (fc != null) return fc.getLocalName(factoryInstance);

        return null;
    }

    /**
     * Register a factory object with a particular product and descriptor against a specified mode. For modes that do
     * not support preferences the productName is ignored (can be <code>null</code>)
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @param factory the object to be registered.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code> (productName can be <code>null
     *     </code> for modes that do not support preferences).
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public void registerFactory(String modeName, String descriptorName, String productName, Object factory) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        if (factory == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        if (dc.arePreferencesSupported) {

            OperationGraph og = dc.addProduct(descriptorName, productName);

            if (og == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
            }

            og.addOp(new PartialOrderNode(factory, factory.getClass().getName()));
        }

        fc.addFactory(descriptorName, productName, factory);
    }

    /**
     * Unregister a factory object previously registered with a product and descriptor against the specified mode. For
     * modes that do not support preferences the productName is ignored (can be <code>null</code>)
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @param factory the object to be unregistered.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code> (productName can be <code>null
     *     </code> for modes that do not support preferences).
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the factory object was not previously registered against descriptorName and
     *     productName
     * @since JAI 1.1
     */
    public void unregisterFactory(String modeName, String descriptorName, String productName, Object factory) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        if (factory == null) {
            throw new IllegalArgumentException(JaiI18N.getString("Generic0"));
        }

        fc.removeFactory(descriptorName, productName, factory);

        if (dc.arePreferencesSupported) {

            OperationGraph og = dc.lookupProduct(descriptorName, productName);

            if (og == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
            }

            og.removeOp(factory);
        }
    }

    /**
     * Sets a preference between two factory instances for a given operation under a specified product.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @param preferredOp the preferred factory object
     * @param otherOp the other factory object
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if either of the factory objects were not previously registered against
     *     descriptorName and productName
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @since JAI 1.1
     */
    public void setFactoryPreference(
            String modeName, String descriptorName, String productName, Object preferredOp, Object otherOp) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        // This should throw an exception if preferences are not
        // supported.
        fc.setPreference(descriptorName, productName, preferredOp, otherOp);

        if (dc.arePreferencesSupported) {

            OperationGraph og = dc.lookupProduct(descriptorName, productName);

            if (og == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
            }

            og.setPreference(preferredOp, otherOp);
        }
    }

    /**
     * Unsets a preference between two factory instances for a given operation under a specified product.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @param preferredOp the factory object formerly preferred
     * @param otherOp the other factory object
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if either of the factory objects were not previously registered against
     *     descriptorName and productName
     * @throws IllegalArgumentException if the registry mode does not support preferences
     * @since JAI 1.1
     */
    public void unsetFactoryPreference(
            String modeName, String descriptorName, String productName, Object preferredOp, Object otherOp) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        // This should throw an exception if preferences are not
        // supported.
        fc.unsetPreference(descriptorName, productName, preferredOp, otherOp);

        if (dc.arePreferencesSupported) {

            OperationGraph og = dc.lookupProduct(descriptorName, productName);

            if (og == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
            }

            og.unsetPreference(preferredOp, otherOp);
        }
    }

    /**
     * Removes all preferences between instances of a factory within a product registered under a particular <code>
     * OperationDescriptor</code>.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public void clearFactoryPreferences(String modeName, String descriptorName, String productName) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        Object prefs[][] = fc.getPreferences(descriptorName, productName);

        if (prefs != null) {

            OperationGraph og = dc.lookupProduct(descriptorName, productName);

            if (og == null) {
                throw new IllegalArgumentException(
                        JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
            }

            for (int i = 0; i < prefs.length; i++) {
                og.unsetPreference(prefs[i][0], prefs[i][1]);
            }
        }

        fc.clearPreferences(descriptorName, productName);
    }

    /**
     * Get all pairwise preferences between instances of a factory within a product registered under a particular <code>
     * OperationDescriptor</code>.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public Object[][] getFactoryPreferences(String modeName, String descriptorName, String productName) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        return fc.getPreferences(descriptorName, productName);
    }

    /**
     * Returns a list of the factory instances of a product registered under a particular <code>OperationDescriptor
     * </code>, in an ordering that satisfies all of the pairwise preferences that have been set. Returns <code>null
     * </code> if cycles exist. Returns <code>null</code>, if the product does not exist under this descriptorName.
     *
     * <p>If the particular registry mode does not support preferences then the returned <code>List</code> will contain
     * a single factory.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param productName the product name as a <code>String</code>
     * @return an ordered <code>List</code> of factory instances
     * @throws IllegalArgumentException if any of the arguments is <code>null</code> (productName can be <code>null
     *     </code> for modes that do not support preferences).
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public List getOrderedFactoryList(String modeName, String descriptorName, String productName) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        if (dc.arePreferencesSupported) {

            OperationGraph og = dc.lookupProduct(descriptorName, productName);

            if (og == null) return null;

            Vector v = og.getOrderedOperationList();

            if ((v == null) || (v.size() <= 0)) return null;

            ArrayList list = new ArrayList(v.size());

            for (int i = 0; i < v.size(); i++) {
                list.add(((PartialOrderNode) v.elementAt(i)).getData());
            }

            return list;

        } else {
            return fc.getFactoryList(descriptorName, productName);
        }
    }

    /**
     * Returns an <code>Iterator</code> over all factory objects registered with the specified factory and operation
     * names over all products. The order of objects in the iteration will be according to the pairwise preferences
     * among products and image factories within a product. The <code>remove()</code> method of the <code>Iterator
     * </code> may not be implemented. If the particular factory does not have preferences then the returned <code>
     * Iterator</code> will traverse a collection containing the single factory.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @return an <code>Iterator</code> over factory objects
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public Iterator getFactoryIterator(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);
        FactoryCache fc = getFactoryCache(modeName);

        if (dc.getDescriptor(descriptorName) == null) {
            throw new IllegalArgumentException(
                    JaiI18N.formatMsg("OperationRegistry5", new Object[] {descriptorName, modeName}));
        }

        if (dc.arePreferencesSupported) {
            Vector v = getOrderedProductList(modeName, descriptorName);

            if ((v == null) || (v.size() <= 0)) return null;

            ArrayList list = new ArrayList();

            List plist;

            for (int i = 0; i < v.size(); i++) {
                plist = getOrderedFactoryList(modeName, descriptorName, (String) v.get(i));
                if (plist != null) list.addAll(plist);
            }

            return list.iterator();

        } else {
            List list = fc.getFactoryList(descriptorName, null);

            if (list != null) return list.iterator();
        }

        return null;
    }

    /**
     * Returns the factory of the specified type for the named operation. This method will return the first factory that
     * would be encountered by the <code>Iterator</code> returned by the <code>getFactoryIterator()</code> method.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @return a registered factory object
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public Object getFactory(String modeName, String descriptorName) {

        Iterator it = getFactoryIterator(modeName, descriptorName);

        if ((it != null) && it.hasNext()) return it.next();

        return null;
    }

    /**
     * Finds the factory of the specified type for the named operation and invokes its default factory method with the
     * supplied parameters. The class of the returned object is that of the object returned by the factory's object
     * creation method.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @return an object created by the factory method
     * @throws IllegalArgumentException if modeName or descriptorName is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @since JAI 1.1
     */
    public Object invokeFactory(String modeName, String descriptorName, Object[] args) {

        Iterator it = getFactoryIterator(modeName, descriptorName);

        if (it == null) return null;

        FactoryCache fc = getFactoryCache(modeName);
        ImagingListener listener = JAI.getDefaultInstance().getImagingListener();
        Exception savedOne = null;

        while (it.hasNext()) {

            Object factory = it.next();
            Object obj;

            try {
                if ((obj = fc.invoke(factory, args)) != null) return obj;
                savedOne = null;
            } catch (Exception e) {
                listener.errorOccurred(
                        JaiI18N.getString("OperationRegistry6") + " \"" + descriptorName + "\"", e, this, false);
                savedOne = e;
                //		e.printStackTrace();
            }
        }

        if (savedOne != null)
            throw new ImagingException(
                    JaiI18N.getString("OperationRegistry7") + " \"" + descriptorName + "\"", savedOne);

        return null;
    }

    //////////////////
    //
    // Property related methods :
    // If a RegistryElementDescriptor supports properties
    // (arePropertiesSupported() return true) then a property
    // environment has to be managed.
    //
    // In the next four methods if the mode is null then apply to all modes
    // that support properties.

    /**
     * Adds a <code>PropertyGenerator</code> to the registry, associating it with a particular descriptor registered
     * against a registry mode.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param generator the <code>PropertyGenerator</code> to be added.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public void addPropertyGenerator(String modeName, String descriptorName, PropertyGenerator generator) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.addPropertyGenerator(descriptorName, generator);
    }

    /**
     * Removes a <code>PropertyGenerator</code> from its association with a particular descriptor/registry-mode in the
     * registry. If the generator was not associated with the operation, nothing happens.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param generator the <code>PropertyGenerator</code> to be removed.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public void removePropertyGenerator(String modeName, String descriptorName, PropertyGenerator generator) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.removePropertyGenerator(descriptorName, generator);
    }

    /**
     * Forces a property to be copied from the specified source by nodes performing a particular operation. By default,
     * a property is copied from the first source node that emits it. The result of specifying an invalid source is
     * undefined.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param propertyName the name of the property to be copied.
     * @param sourceIndex the index of the source to copy the property from.
     * @throws IllegalArgumentException if any of the <code>String</code> arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public void copyPropertyFromSource(String modeName, String descriptorName, String propertyName, int sourceIndex) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.copyPropertyFromSource(descriptorName, propertyName, sourceIndex);
    }

    /**
     * Forces a particular property to be suppressed by nodes performing a particular operation. By default, properties
     * are passed through operations unchanged.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param propertyName the name of the property to be suppressed.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public void suppressProperty(String modeName, String descriptorName, String propertyName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.suppressProperty(descriptorName, propertyName);
    }

    /**
     * Forces all properties to be suppressed by nodes performing a particular operation. By default, properties are
     * passed through operations unchanged.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public void suppressAllProperties(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.suppressAllProperties(descriptorName);
    }

    /**
     * Removes all property associated information for this registry mode from this <code>OperationRegistry</code>.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @throws IllegalArgumentException if modeName is null or is not one of those returned by <code>
     *     RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public void clearPropertyState(String modeName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) dc.clearPropertyState();
    }

    /**
     * Returns a list of the properties generated by nodes implementing the descriptor associated with a particular
     * descriptor Name. Returns null if no properties are generated.
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public String[] getGeneratedPropertyNames(String modeName, String descriptorName) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getGeneratedPropertyNames(descriptorName);

        return null;
    }

    /**
     * Merge mode-specific property environment with mode-independent property environment of the descriptor. Array
     * elements of "sources" are expected to be in the same ordering as referenced by the "sourceIndex" parameter of
     * copyPropertyFromSource().
     *
     * @param modeName the registry mode name as a <code>String</code>
     * @param descriptorName the descriptor name as a <code>String</code>
     * @param op the <code>Object</code> from which properties will be generated.
     * @param sources the <code>PropertySource</code>s corresponding to the sources of the object representing the named
     *     descriptor in the indicated mode. The supplied <code>Vector</code> may be empty to indicate that there are no
     *     sources.
     * @return A <code>PropertySource</code> which encapsulates the global property environment for the object
     *     representing the named descriptor in the indicated mode.
     * @throws IllegalArgumentException if any of the arguments is <code>null</code>
     * @throws IllegalArgumentException if modeName is not one of those returned by <code>RegistryMode.getModes()</code>
     * @throws IllegalArgumentException if there is no <code>
     *             RegistryElementDescriptor</code> registered against the <code>descriptorName</code>
     * @throws IllegalArgumentException if the specified mode does not support properties.
     * @since JAI 1.1
     */
    public PropertySource getPropertySource(String modeName, String descriptorName, Object op, Vector sources) {

        DescriptorCache dc = getDescriptorCache(modeName);

        if (dc != null) return dc.getPropertySource(descriptorName, op, sources);

        return null;
    }

    /**
     * Constructs and returns a <code>PropertySource</code> suitable for use by a given <code>OperationNode</code>. The
     * <code>PropertySource</code> includes properties copied from prior nodes as well as those generated at the node
     * itself. Additionally, property suppression is taken into account. The actual implementation of <code>
     * getPropertySource()</code> may make use of deferred execution and caching.
     *
     * @param op the <code>OperationNode</code> requesting its <code>PropertySource</code>.
     * @throws IllegalArgumentException if op is null.
     * @since JAI 1.1
     */
    public PropertySource getPropertySource(OperationNode op) {

        if (op == null) throw new IllegalArgumentException(JaiI18N.getString("Generic0"));

        // Get the source Vector from the ParameterBlock.
        ParameterBlock pb = op.getParameterBlock();
        Vector pv = (pb == null) ? null : pb.getSources();

        // If the source Vector is null, replace it by a zero-length
        // Vector. This tricks the DescriptorCache into accepting the
        // parameter and the PropertyEnvironment object created in
        // the DescriptorCache works with either a null or zero-length
        // source Vector.
        if (pv == null) {
            pv = new Vector();
        }

        return getPropertySource(op.getRegistryModeName(), op.getOperationName(), op, pv);
    }

    /**
     * Load all the "META-INF/registryFile.jai" files and then called the <code>updateRegistry()</code> of the
     * registered service provider of <code>OperationRegistrySpi</code> found in the classpath corresponding to this
     * class loader. All non-IO exceptions encountered while parsing the registry files are caught and their error
     * messages are redirected to <code>System.err</code>. If <code>System.err</code> is null the error messages will
     * never be seen.
     *
     * <p>This is a convenience method to do automatic detection in runtime loaded jar files
     *
     * <p>Note that the JAI does not keep track of which JAR files have their registry files loaded and/or services
     * initialized. Hence if <code>registerServices</code> is called twice with the same ClassLoader, the loading of the
     * registry files and/or initialization of the services will happen twice.
     *
     * @since JAI 1.1
     */
    public void registerServices(ClassLoader cl) throws IOException {

        // First load all the REGISTRY_FILEs that are found in
        // the specified class loader.
        Enumeration en;

        if (cl == null) en = ClassLoader.getSystemResources(USR_REGISTRY_FILE);
        else en = cl.getResources(USR_REGISTRY_FILE);

        while (en.hasMoreElements()) {
            URL url = (URL) en.nextElement();

            RegistryFileParser.loadOperationRegistry(this, cl, url);
        }

        // Now call the "updateRegistry" method for all OperationRegistry
        // service providers.
        Iterator spitr;

        if (cl == null) spitr = Service.providers(OperationRegistrySpi.class);
        else spitr = Service.providers(OperationRegistrySpi.class, cl);

        while (spitr.hasNext()) {

            OperationRegistrySpi ospi = (OperationRegistrySpi) spitr.next();
            ospi.updateRegistry(this);
        }
    }

    /** ******************** DEPRECATED METHODS ************************ */

    // OperationDescriptor methods

    /**
     * Registers an <code>OperationDescriptor</code> with the registry. Each operation must have an <code>
     * OperationDescriptor</code> before registerRIF() may be called to add RIFs to the operation.
     *
     * @param odesc an <code>OperationDescriptor</code> containing information about the operation.
     * @param operationName the operation name as a String.
     * @deprecated as of JAI 1.1 in favor of <code>registerDescriptor(odesc)</code>
     * @see #registerDescriptor(RegistryElementDescriptor) registerDescriptor - for list of exceptions thrown.
     */
    public void registerOperationDescriptor(OperationDescriptor odesc, String operationName) {
        registerDescriptor(odesc);
    }

    /**
     * Unregisters an <code>OperationDescriptor</code> from the registry.
     *
     * @param operationName the operation name as a String.
     * @deprecated as of JAI 1.1 in favor of <code>unregisterDescriptor(...)
     * 	</code> which accepts an <code>OperationDescriptor</code> and not a <code>operationName</code>.
     * @see #unregisterDescriptor(RegistryElementDescriptor) unregisterDescriptor - for list of exceptions thrown.
     */
    public void unregisterOperationDescriptor(String operationName) {

        String[] operationModes = RegistryMode.getModeNames(OperationDescriptor.class);

        RegistryElementDescriptor red;

        for (int i = 0; i < operationModes.length; i++) {
            if ((red = getDescriptor(operationModes[i], operationName)) != null) unregisterDescriptor(red);
        }
    }

    /**
     * Returns the <code>OperationDescriptor</code> that is currently registered under the given name, or null if none
     * exists. Though unlikely, it is possible to have different descriptors registered under different modes. In this
     * case this will arbitrarily return the first operation descriptor found.
     *
     * @param operationName the String to be queried.
     * @return an <code>OperationDescriptor</code>.
     * @throws IllegalArgumentException if operationName is null.
     * @deprecated as of JAI 1.1 in favor of <code>getDescriptor(...)</code> where the mode name is explicitly
     *     specified.
     * @see #getDescriptor(Class, String)
     */
    public OperationDescriptor getOperationDescriptor(String operationName) {
        return (OperationDescriptor) getDescriptor(OperationDescriptor.class, operationName);
    }

    /**
     * Returns a Vector of all currently registered <code>OperationDescriptor</code>s.
     *
     * @return a Vector of <code>OperationDescriptor</code>s.
     * @deprecated as of JAI 1.1 in favor of <code>getDescriptors(
     * OperationDescriptor.class)</code> which returns a <code>List</code> and not a <code>Vector</code>. This is
     *     currently equivalent to <code>new Vector(getDescriptors(OperationDescriptor.class))</code>
     * @see #getDescriptors(Class)
     */
    public Vector getOperationDescriptors() {
        List list = getDescriptors(OperationDescriptor.class);

        return list == null ? null : new Vector(list);
    }

    /**
     * Returns a list of names under which all the <code>OperationDescriptor</code>s in the registry are registered.
     *
     * @return a list of currently existing operation names.
     * @deprecated as of JAI 1.1 in favor of <code>getDescriptorNames(
     * 	OperationDescriptor.class)</code>.
     * @see #getDescriptorNames(Class)
     */
    public String[] getOperationNames() {
        return getDescriptorNames(OperationDescriptor.class);
    }

    // RenderedImageFactory methods

    /**
     * Registers a RIF with a particular product and operation.
     *
     * @param operationName the operation name as a String.
     * @param productName the product name, as a String.
     * @param RIF the <code>RenderedImageFactory</code> to be registered.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.register(...)
     *     </code>. This is currently equivalent to <code>
     *     RIFRegistry.register(this, operationName, productName, RIF)</code>
     * @see #registerFactory registerFactory - for list of exceptions thrown.
     * @see RIFRegistry#register
     */
    public void registerRIF(String operationName, String productName, RenderedImageFactory RIF) {

        registerFactory(RenderedRegistryMode.MODE_NAME, operationName, productName, RIF);
    }

    /**
     * Unregisters a RIF from a particular product and operation.
     *
     * @param operationName the operation name as a String.
     * @param productName the product name, as a String.
     * @param RIF the <code>RenderedImageFactory</code> to be unregistered.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.unregister(...)
     *     </code>. This is currently equivalent to <code>
     *     RIFRegistry.unregister(this, operationName, productName, RIF)</code>
     * @see #unregisterFactory unregisterFactory - for list of exceptions thrown.
     * @see RIFRegistry#unregister
     */
    public void unregisterRIF(String operationName, String productName, RenderedImageFactory RIF) {

        unregisterFactory(RenderedRegistryMode.MODE_NAME, operationName, productName, RIF);
    }

    /**
     * Registers a CRIF under a particular operation.
     *
     * @param operationName the operation name as a String.
     * @param CRIF the <code>ContextualRenderedImageFactory</code> to be registered.
     * @deprecated as of JAI 1.1 in favor of <code>CRIFRegistry.register(...)
     *     </code>. This is currently equivalent to <code>
     *     CRIFRegistry.register(this, operationName, productName, CRIF)</code>
     * @see #registerFactory registerFactory - for list of exceptions thrown.
     * @see CRIFRegistry#register
     */
    public void registerCRIF(String operationName, ContextualRenderedImageFactory CRIF) {

        registerFactory(RenderableRegistryMode.MODE_NAME, operationName, null, CRIF);
    }

    /**
     * Unregisters a CRIF from a particular operation.
     *
     * @param operationName the operation name as a String.
     * @param CRIF the <code>ContextualRenderedImageFactory</code> to be unregistered.
     * @deprecated as of JAI 1.1 in favor of <code>CRIFRegistry.unregister(...)
     *     </code>. This is currently equivalent to <code>
     *     CRIFRegistry.unregister(this, operationName, productName, CRIF)</code>
     * @see #unregisterFactory unregisterFactory - for list of exceptions thrown.
     * @see CRIFRegistry#unregister
     */
    public void unregisterCRIF(String operationName, ContextualRenderedImageFactory CRIF) {

        unregisterFactory(RenderableRegistryMode.MODE_NAME, operationName, null, CRIF);
    }

    /**
     * Registers a CIF with a particular product and operation.
     *
     * @param operationName the operation name as a String.
     * @param productName the product name, as a String.
     * @param CIF the <code>CollectionImageFactory</code> to be registered.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.register(...)
     *     </code>. This is currently equivalent to <code>
     *     CIFRegistry.register(this, operationName, productName, CIF)</code>
     * @see #registerFactory registerFactory - for list of exceptions thrown.
     * @see CIFRegistry#register
     */
    public void registerCIF(String operationName, String productName, CollectionImageFactory CIF) {

        registerFactory(CollectionRegistryMode.MODE_NAME, operationName, productName, CIF);
    }

    /**
     * Unregisters a CIF from a particular product and operation.
     *
     * @param operationName the operation name as a String.
     * @param productName the product name, as a String.
     * @param CIF the <code>CollectionImageFactory</code> to be unregistered.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.unregister(...)
     *     </code>. This is currently equivalent to <code>
     *     CIFRegistry.unregister(this, operationName, productName, CIF)</code>
     * @see #unregisterFactory unregisterFactory - for list of exceptions thrown.
     * @see CIFRegistry#unregister
     */
    public void unregisterCIF(String operationName, String productName, CollectionImageFactory CIF) {

        unregisterFactory(CollectionRegistryMode.MODE_NAME, operationName, productName, CIF);
    }

    // Product preferences

    /**
     * Sets a preference between two products registered under a common <code>OperationDescriptor</code>. Any attempt to
     * set a preference between a product and itself will be ignored.
     *
     * @param operationName the operation name as a String.
     * @param preferredProductName the product to be preferred.
     * @param otherProductName the other product.
     * @deprecated as of JAI 1.1 in favor of <code>setProductPreference(...)
     *     </code> which specifies a <code>modeName</code> also. This is currently equivalent to <code>
     *     setProductPreference("rendered",
     *     operationName, preferredProductName, otherProductName)</code>
     * @see #setProductPreference setProductPreference - for list of exceptions thrown.
     */
    public void setProductPreference(String operationName, String preferredProductName, String otherProductName) {

        setProductPreference(RenderedRegistryMode.MODE_NAME, operationName, preferredProductName, otherProductName);
    }

    /**
     * Removes a preference between two products registered under a common <code>OperationDescriptor</code>.
     *
     * @param operationName the operation name as a String.
     * @param preferredProductName the product formerly preferred.
     * @param otherProductName the other product.
     * @deprecated as of JAI 1.1 in favor of <code>unsetProductPreference(...)
     *     </code> which specifies a <code>modeName</code> also. This is currently equivalent to <code>
     *     unsetProductPreference("rendered",
     *     operationName, preferredProductName, otherProductName)</code>
     * @see #unsetProductPreference unsetProductPreference - for list of exceptions thrown.
     */
    public void unsetProductPreference(String operationName, String preferredProductName, String otherProductName) {

        unsetProductPreference(RenderedRegistryMode.MODE_NAME, operationName, preferredProductName, otherProductName);
    }

    /**
     * Removes all preferences between products registered under a common <code>OperationDescriptor</code>.
     *
     * @param operationName the operation name as a String.
     * @deprecated as of JAI 1.1 in favor of <code>clearProductPreferences(...)
     *     </code> which specifies a <code>modeName</code> also. This is currently equivalent to <code>
     *     clearProductPreferences("rendered", operationName)</code>
     * @see #clearProductPreferences clearProductPreferences - for list of exceptions thrown.
     */
    public void clearProductPreferences(String operationName) {

        clearProductPreferences(RenderedRegistryMode.MODE_NAME, operationName);
    }

    /**
     * Returns a list of the pairwise product preferences under a particular <code>OperationDescriptor</code>. If no
     * product preferences have been set, returns null.
     *
     * @param operationName the operation name as a String.
     * @return an array of 2-element arrays of Strings.
     * @deprecated as of JAI 1.1 in favor of <code>getProductPreferences(...)
     *     </code> which accepts a <code>modeName</code> also. This is currently equivalent to <code>
     *     getProductPreferences("rendered", operationName)</code>
     * @see #getProductPreferences getProductPreferences - for list of exceptions thrown.
     */
    public String[][] getProductPreferences(String operationName) {

        return getProductPreferences(RenderedRegistryMode.MODE_NAME, operationName);
    }

    /**
     * Returns a list of the products registered under a particular <code>OperationDescriptor</code>, in an ordering
     * that satisfies all of the pairwise preferences that have been set. Returns <code>null</code> if cycles exist.
     * Returns <code>null</code> if no <code>OperationDescriptor</code> has been registered under this operationName, or
     * if no products exist for this operation.
     *
     * @param operationName the operation name as a String.
     * @return a Vector of Strings representing product names.
     * @deprecated as of JAI 1.1 in favor of <code>getOrderedProductList(...)
     *     </code> which accepts a <code>modeName</code> also. This is currently equivalent to <code>
     *     getOrderedProductList("rendered", operationName)</code>
     * @see #getOrderedProductList getOrderedProductList - for list of exceptions thrown.
     */
    public Vector getOrderedProductList(String operationName) {

        return getOrderedProductList(RenderedRegistryMode.MODE_NAME, operationName);
    }

    // Operation preferences (within a product)

    /**
     * Sets a preference between two RIFs within the same product. Any attempt to set a preference between a RIF and
     * itself will be ignored.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @param preferredRIF the preferred <code>RenderedImageFactory</code>.
     * @param otherRIF the other <code>RenderedImageFactory</code>.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.setPreference(...)
     *     </code>. This is currently equivalent to <code>
     *     RIFRegistry.setPreference(this, operationName, productName,
     *     preferredRIF, otherRIF)</code>
     * @see #setFactoryPreference setFactoryPreference - for list of exceptions thrown.
     * @see RIFRegistry#setPreference
     */
    public void setRIFPreference(
            String operationName,
            String productName,
            RenderedImageFactory preferredRIF,
            RenderedImageFactory otherRIF) {

        setFactoryPreference(RenderedRegistryMode.MODE_NAME, operationName, productName, preferredRIF, otherRIF);
    }

    /**
     * Sets a preference between two CIFs within the same product. Any attempt to set a preference between a CIF and
     * itself will be ignored.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @param preferredCIF the preferred CollectionRenderedImageFactory.
     * @param otherCIF the other CollectionRenderedImageFactory.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.setPreference(...)
     *     </code>. This is currently equivalent to <code>
     *     CIFRegistry.setPreference(this, operationName, productName,
     *     preferredCIF, otherCIF)</code>
     * @see #setFactoryPreference setFactoryPreference - for list of exceptions thrown.
     * @see CIFRegistry#setPreference
     */
    public void setCIFPreference(
            String operationName,
            String productName,
            CollectionImageFactory preferredCIF,
            CollectionImageFactory otherCIF) {

        setFactoryPreference(CollectionRegistryMode.MODE_NAME, operationName, productName, preferredCIF, otherCIF);
    }

    /**
     * Removes a preference between two RIFs within the same product.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @param preferredRIF the formerly preferred <code>RenderedImageFactory</code>.
     * @param otherRIF the other <code>RenderedImageFactory</code>.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.unsetPreference(...)
     *     </code>. This is currently equivalent to <code>
     *     RIFRegistry.unsetPreference(this, operationName, productName,
     *     preferredRIF, otherRIF)</code>
     * @see #unsetFactoryPreference unsetFactoryPreference - for list of exceptions thrown.
     * @see RIFRegistry#unsetPreference
     */
    public void unsetRIFPreference(
            String operationName,
            String productName,
            RenderedImageFactory preferredRIF,
            RenderedImageFactory otherRIF) {

        unsetFactoryPreference(RenderedRegistryMode.MODE_NAME, operationName, productName, preferredRIF, otherRIF);
    }

    /**
     * Removes a preference between two CIFs within the same product.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @param preferredCIF the formerly preferred <code>CollectionImageFactory</code>.
     * @param otherCIF the other <code>CollectionImageFactory</code>.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.unsetPreference(...)
     *     </code>. This is currently equivalent to <code>
     *     CIFRegistry.unsetPreference(this, operationName, productName,
     *     preferredCIF, otherCIF)</code>
     * @see #unsetFactoryPreference unsetFactoryPreference - for list of exceptions thrown.
     * @see CIFRegistry#unsetPreference
     */
    public void unsetCIFPreference(
            String operationName,
            String productName,
            CollectionImageFactory preferredCIF,
            CollectionImageFactory otherCIF) {

        unsetFactoryPreference(CollectionRegistryMode.MODE_NAME, operationName, productName, preferredCIF, otherCIF);
    }

    /**
     * Removes all preferences between RIFs within a product registered under a particular <code>OperationDescriptor
     * </code>.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.clearPreferences(...)
     *     </code>. This is currently equivalent to <code>
     *     RIFRegistry.clearPreferences(this, operationName, productName)</code>
     * @see #clearFactoryPreferences clearFactoryPreferences - for list of exceptions thrown.
     * @see RIFRegistry#clearPreferences
     */
    public void clearRIFPreferences(String operationName, String productName) {

        clearFactoryPreferences(RenderedRegistryMode.MODE_NAME, operationName, productName);
    }

    /**
     * Removes all preferences between CIFs within a product registered under a particular <code>OperationDescriptor
     * </code>.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.clearPreferences(...)
     *     </code>. This is currently equivalent to <code>
     *     CIFRegistry.clearPreferences(this, operationName, productName)</code>
     * @see #clearFactoryPreferences clearFactoryPreferences - for list of exceptions thrown.
     * @see CIFRegistry#clearPreferences
     */
    public void clearCIFPreferences(String operationName, String productName) {

        clearFactoryPreferences(CollectionRegistryMode.MODE_NAME, operationName, productName);
    }

    /**
     * Removes all RIF and CIF preferences within a product registered under a particular <code>OperationDescriptor
     * </code>.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @deprecated as of JAI 1.1 in favor of calling <code>
     *     *IFRegistry.clearPreferences(..)</code> on all image operation related modes.
     * @see #clearFactoryPreferences clearFactoryPreferences - for list of exceptions thrown.
     * @see RIFRegistry#clearPreferences
     * @see CIFRegistry#clearPreferences
     */
    public void clearOperationPreferences(String operationName, String productName) {

        String[] operationModes = RegistryMode.getModeNames(OperationDescriptor.class);

        for (int i = 0; i < operationModes.length; i++) {

            DescriptorCache dc = getDescriptorCache(operationModes[i]);

            if (!dc.arePreferencesSupported) continue;

            if (getDescriptor(operationModes[i], operationName) == null) continue;

            clearFactoryPreferences(operationModes[i], operationName, productName);
        }
    }

    /**
     * Returns a list of the RIFs of a product registered under a particular <code>OperationDescriptor</code>, in an
     * ordering that satisfies all of the pairwise preferences that have been set. Returns <code>null</code> if cycles
     * exist. Returns <code>null</code>, if the product does not exist under this operationName.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @return a Vector of RIFs.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.getOrderedList(...)
     *     </code> which returns a <code>List</code> and not a <code>Vector</code>. This is currently equivalent to
     *     <code>
     *     new Vector(RIFRegistry.getOrderedList(this, operationName,
     *     productName))</code>
     * @see #getOrderedFactoryList getOrderedFactoryList - for list of exceptions thrown.
     * @see RIFRegistry#getOrderedList
     */
    public Vector getOrderedRIFList(String operationName, String productName) {

        List list = getOrderedFactoryList(RenderedRegistryMode.MODE_NAME, operationName, productName);

        return list == null ? null : new Vector(list);
    }

    /**
     * Returns a list of the CIFs of a product registered under a particular <code>OperationDescriptor</code>, in an
     * ordering that satisfies all of the pairwise preferences that have been set. Returns <code>null</code> if cycles
     * exist. Returns <code>null</code>, if the product does not exist under this operationName.
     *
     * @param operationName the operation name as a String.
     * @param productName the name of the product.
     * @return a Vector of CIFs.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.getOrderedList(...)
     *     </code> which returns a <code>List</code> and not a <code>Vector</code>. This is currently equivalent to
     *     <code>
     *     new Vector(CIFRegistry.getOrderedList(this, operationName,
     *     productName))</code>
     * @see #getOrderedFactoryList getOrderedFactoryList - for list of exceptions thrown.
     * @see CIFRegistry#getOrderedList
     */
    public Vector getOrderedCIFList(String operationName, String productName) {

        List list = getOrderedFactoryList(CollectionRegistryMode.MODE_NAME, operationName, productName);

        return list == null ? null : new Vector(list);
    }

    // Create methods

    /**
     * Constructs a PlanarImage (usually a <code>RenderedOp</code>) representing the results of applying a given
     * operation to a particular ParameterBlock and rendering hints. The registry is used to determine the RIF to be
     * used to instantiate the operation.
     *
     * <p>If none of the RIFs registered with this <code>OperationRegistry</code> returns a non-null value, null is
     * returned. Exceptions thrown by the RIFs will be caught by this method and will not be propagated.
     *
     * @param operationName the operation name as a String.
     * @param paramBlock the operation's ParameterBlock.
     * @param renderHints a RenderingHints object containing rendering hints.
     * @throws IllegalArgumentException if operationName is null.
     * @deprecated as of JAI 1.1 in favor of <code>RIFRegistry.create(...)
     *     </code> which returns a <code>RenderedImage</code> and not a <code>PlanarImage</code>. This is currently
     *     equivalent to <code>
     *     PlanarImage.wrapRenderedImage(RIFRegistry.create(this,
     *     operationName, paramBlock, renderHints))</code>
     * @see RIFRegistry#create
     */
    public PlanarImage create(String operationName, ParameterBlock paramBlock, RenderingHints renderHints) {
        return PlanarImage.wrapRenderedImage(RIFRegistry.create(this, operationName, paramBlock, renderHints));
    }

    /**
     * Constructs the CRIF to be used to instantiate the operation. Returns null, if no CRIF is registered with the
     * given operation name.
     *
     * @param operationName the operation name as a String.
     * @param paramBlock the operation's ParameterBlock.
     * @throws IllegalArgumentException if operationName is null.
     * @deprecated as of JAI 1.1 in favor of <code>CRIFRegistry.get(...)</code> This is currently equivalent to <code>
     *     CRIFRegistry.get(this,
     *     operationName)</code>
     * @see CRIFRegistry#get
     */
    public ContextualRenderedImageFactory createRenderable(String operationName, ParameterBlock paramBlock) {

        return CRIFRegistry.get(this, operationName);
    }

    /**
     * Constructs a <code>CollectionImage</code> (usually a <code>CollectionOp</code>) representing the results of
     * applying a given operation to a particular ParameterBlock and rendering hints. The registry is used to determine
     * the CIF to be used to instantiate the operation.
     *
     * <p>If none of the CIFs registered with this <code>OperationRegistry</code> returns a non-null value, null is
     * returned. Exceptions thrown by the CIFs will be caught by this method and will not be propagated.
     *
     * @param operationName The operation name as a String.
     * @param args The operation's input parameters.
     * @param hints A RenderingHints object containing rendering hints.
     * @throws IllegalArgumentException if operationName is null.
     * @deprecated as of JAI 1.1 in favor of <code>CIFRegistry.create(...)
     *     </code>. This is currently equivalent to <code>
     *     CIFRegistry.create(this, operationName, args, hints))</code>
     * @see CIFRegistry#create
     */
    public CollectionImage createCollection(String operationName, ParameterBlock args, RenderingHints hints) {
        return CIFRegistry.create(this, operationName, args, hints);
    }

    // Property management

    /**
     * Removes all property associated information from this <code>OperationRegistry</code>.
     *
     * @deprecated as of JAI 1.1 in factor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>
     * clearPropertyState("rendered")</code>
     * @see #clearPropertyState
     */
    public void clearPropertyState() {

        clearPropertyState(RenderedRegistryMode.MODE_NAME);
    }

    /**
     * Adds a <code>PropertyGenerator</code> to the registry, associating it with a particular <code>OperationDescriptor
     * </code>.
     *
     * @param operationName the operation name as a String.
     * @param generator the <code>PropertyGenerator</code> to be added.
     * @deprecated as of JAI 1.1 in favor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>addPropertyGenerator("rendered", ...)</code>
     * @see #addPropertyGenerator addPropertyGenerator - for list of exceptions thrown.
     */
    public void addPropertyGenerator(String operationName, PropertyGenerator generator) {

        addPropertyGenerator(RenderedRegistryMode.MODE_NAME, operationName, generator);
    }

    /**
     * Removes a <code>PropertyGenerator</code> from its association with a particular <code>OperationDescriptor</code>
     * in the registry. If the generator was not associated with the operation, nothing happens.
     *
     * @param operationName the operation name as a String.
     * @param generator the <code>PropertyGenerator</code> to be removed.
     * @deprecated as of JAI 1.1 in favor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>removePropertyGenerator("rendered", ...)</code>
     * @see #removePropertyGenerator removePropertyGenerator - for list of exceptions thrown.
     */
    public void removePropertyGenerator(String operationName, PropertyGenerator generator) {

        removePropertyGenerator(RenderedRegistryMode.MODE_NAME, operationName, generator);
    }

    /**
     * Forces a particular property to be suppressed by nodes performing a particular operation. By default, properties
     * are passed through operations unchanged.
     *
     * @param operationName the operation name as a String.
     * @param propertyName the name of the property to be suppressed.
     * @deprecated as of JAI 1.1 in favor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>suppressProperty("rendered", ...)</code>
     * @see #suppressProperty suppressProperty - for list of exceptions thrown.
     */
    public void suppressProperty(String operationName, String propertyName) {

        suppressProperty(RenderedRegistryMode.MODE_NAME, operationName, propertyName);
    }

    /**
     * Forces all properties to be suppressed by nodes performing a particular operation. By default, properties are
     * passed through operations unchanged.
     *
     * @param operationName the operation name as a String.
     * @deprecated as of JAI 1.1 in favor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>suppressAllProperties("rendered", ...)</code>
     * @see #suppressAllProperties suppressAllProperties - for list of exceptions thrown.
     */
    public void suppressAllProperties(String operationName) {

        suppressAllProperties(RenderedRegistryMode.MODE_NAME, operationName);
    }

    /**
     * Forces a property to be copied from the specified source image by <code>RenderedOp</code> nodes performing a
     * particular operation. By default, a property is copied from the first source node that emits it. The result of
     * specifying an invalid source is undefined.
     *
     * @param operationName the operation name as a String.
     * @param propertyName the name of the property to be copied.
     * @param sourceIndex the index of the source to copy the property from.
     * @deprecated as of JAI 1.1 in favor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>copyPropertyFromSource("rendered", ...)</code>
     * @see #copyPropertyFromSource copyPropertyFromSource - for list of exceptions thrown.
     */
    public void copyPropertyFromSource(String operationName, String propertyName, int sourceIndex) {

        copyPropertyFromSource(RenderedRegistryMode.MODE_NAME, operationName, propertyName, sourceIndex);
    }

    /**
     * Returns a list of the properties generated by nodes implementing the operation associated with a particular
     * Operation Name. Returns null if no properties are generated.
     *
     * @param operationName the operation name as a String.
     * @return an array of Strings.
     * @deprecated as of JAI 1.1 in favor of the version where the modeName is explicitly specified. This is currently
     *     equivalent to <code>getGeneratedPropertyNames("rendered", ...)</code>
     * @see #getGeneratedPropertyNames getGeneratedPropertyNames - for list of exceptions thrown.
     */
    public String[] getGeneratedPropertyNames(String operationName) {

        return getGeneratedPropertyNames(RenderedRegistryMode.MODE_NAME, operationName);
    }

    /**
     * Constructs and returns a <code>PropertySource</code> suitable for use by a given <code>RenderedOp</code>. The
     * <code>PropertySource</code> includes properties copied from prior nodes as well as those generated at the node
     * itself. Additionally, property suppression is taken into account. The actual implementation of <code>
     * getPropertySource()</code> may make use of deferred execution and caching.
     *
     * @param op the <code>RenderedOp</code> requesting its <code>PropertySource</code>.
     * @deprecated as of JAI 1.1 in favor <code>RIFRegistry.getPropertySource(op)</code>
     * @see RIFRegistry#getPropertySource #getPropertySource - for list of exceptions thrown.
     */
    public PropertySource getPropertySource(RenderedOp op) {

        return RIFRegistry.getPropertySource(op);
    }

    /**
     * Constructs and returns a <code>PropertySource</code> suitable for use by a given <code>RenderableOp</code>. The
     * <code>PropertySource</code> includes properties copied from prior nodes as well as those generated at the node
     * itself. Additionally, property suppression is taken into account. The actual implementation of <code>
     * getPropertySource()</code> may make use of deferred execution and caching.
     *
     * @param op the <code>RenderableOp</code> requesting its <code>PropertySource</code>.
     * @deprecated as of JAI 1.1 in favor <code>CRIFRegistry.getPropertySource(op)</code>
     * @see CRIFRegistry#getPropertySource
     */
    public PropertySource getPropertySource(RenderableOp op) {

        return CRIFRegistry.getPropertySource(op);
    }
}
